package org.tulkas.robot.browser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import javax.xml.transform.TransformerException;

import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.entity.ByteArrayEntity;
import org.tulkas.robot.exception.ParseException;
import org.tulkas.robot.exception.SystemException;
import org.tulkas.robot.http.BaseConn;
import org.tulkas.robot.http.ClientOptions;
import org.tulkas.robot.http.headers.HttpMethods;
import org.tulkas.robot.http.headers.RequestHeaders;
import org.tulkas.util.HttpUtil;
import org.tulkas.util.XmlDomUtil;
import org.w3c.dom.Document;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public class XhrCall extends BaseConn {
	/**
	 * the method of the xhr call (e.g. GET, POST, PUT...)
	 */
	private String method;

	/**
	 * the raw request object in the form of byte[], string, xml Document, or JSONObject.
	 */
	protected Object rawRequest;

	/**
	 * the raw response in the form of byte array that are recieved from server side.
	 */
	protected byte[] rawResponse;

	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	protected byte[] getRequestBytes() throws ParseException {
		if (rawRequest == null)
			return null;

		String ct = getRequestHeaders().getContentType();
		String cs = HttpUtil.parseCharsetFromContentType(ct);

		try {
			if (rawRequest instanceof byte[])
				return (byte[]) rawRequest;
			else if (rawRequest instanceof String)
				return ((String) rawRequest).getBytes(cs);
			else if (rawRequest instanceof Document) {
				try {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					XmlDomUtil.saveToStream((Document) rawRequest, baos);
					byte[] ret = baos.toByteArray();
					baos.close();
					return ret;
				} catch (TransformerException e) {
					throw new ParseException(e);
				} catch (IOException e) {
					throw new ParseException(e);
				}
			}
		} catch (IOException e) {
			throw new ParseException(e);
		}
		return null;
	}

	public void setRequestAsBytes(byte[] bytes) {
		rawRequest = bytes;
	}

	public byte[] getResponseAsBytes() {
		return rawResponse;
	}

	public void setRawResponse(byte[] rawResponse) {
		this.rawResponse = rawResponse;
	}

	public void setRequestAsString(String string) {
		rawRequest = string;
	}

	public String getResponseAsString() throws ParseException {
		String charset = this.getResponseCharset();
		try {
			if (charset != null) {
				return new String(rawResponse, charset);
			} else {
				return new String(rawResponse);
			}

		} catch (UnsupportedEncodingException e) {
			throw new ParseException(e);
		}
	}

	/**
	 * @param dom
	 */
	public void setRequestAsXml(Document dom) {
		this.rawRequest = dom;
		setRequestContentType(RequestHeaders.MEDIA_TYPEs.XML);
		setRequestCharset(dom.getXmlEncoding());
	}

	/**
	 * @return
	 * @throws ParseException
	 */
	public Document getResponseAsXml() throws ParseException {
		ByteArrayInputStream bais = null;

		try {
			bais = new ByteArrayInputStream(rawResponse);
			return XmlDomUtil.loadFromStream(bais);
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			throw new ParseException(e);
		} finally {
			try {
				bais.close();
			} catch (IOException e) {
				throw new ParseException(e);
			}
		}
	}

	public void setRequestContentType(String contentType) {
		if (contentType == null)
			return;
		String cs = HttpUtil.parseCharsetFromContentType(contentType);
		String ct = getRequestHeaders().getContentType();
		String oriCs = HttpUtil.parseCharsetFromContentType(ct);
		if (cs == null && oriCs != null) {
			ct = contentType + "; charset=" + oriCs;
		} else {
			ct = contentType;
		}
		getRequestHeaders().setContentType(ct);
	}

	public void setRequestCharset(String charset) {
		if (charset == null)
			return;
		String ct = getRequestHeaders().getContentType();
		String mt = HttpUtil.parseMediaTypeFromContentType(ct);
		if (mt != null) {
			ct = mt + "; charset=" + charset;
		} else {
			ct = "; charset=" + charset;
		}
		getRequestHeaders().setContentType(ct);
	}

	public String getResponseContentType() {
		String ct = getResponseHeaders().getContentType();
		return HttpUtil.parseMediaTypeFromContentType(ct);
	}

	public String getResponseCharset() {
		String ct = getResponseHeaders().getContentType();
		return HttpUtil.parseCharsetFromContentType(ct);
	}

	@Override
	public HttpRequestBase createHttpRequest(ClientOptions options) {
		HttpRequestBase req = null;
		if (HttpMethods.GET.equals(method))
			req = new HttpGet(this.getUrl());
		else if (HttpMethods.POST.equals(method))
			req = new HttpPost(this.getUrl());
		else if (HttpMethods.PUT.equals(method))
			req = new HttpPut(this.getUrl());
		else if (HttpMethods.DELETE.equals(method))
			req = new HttpDelete(this.getUrl());
		else if (HttpMethods.HEAD.equals(method))
			req = new HttpHead(this.getUrl());
		else if (HttpMethods.OPTIONS.equals(method))
			req = new HttpOptions(this.getUrl());
		else if (HttpMethods.TRACE.equals(method))
			req = new HttpTrace(this.getUrl());
		else
			throw new SystemException(
					"The http method is illegal or empty in XHR call. It must be one of "
							+ "[GET, POST, PUT, DELETE, HEAD, TRACE, OPTIONS]");

		byte[] rawRequest;
		try {
			rawRequest = this.getRequestBytes();
		} catch (ParseException e) {
			throw new SystemException(e);
		}
		if (rawRequest != null) {
			if (!(req instanceof HttpEntityEnclosingRequestBase))
				throw new SystemException(
						"Http method must be set to POST or PUT if the XhrCall request content are set.");
			HttpEntityEnclosingRequestBase entityReq = (HttpEntityEnclosingRequestBase) req;
			entityReq.setEntity(new ByteArrayEntity(rawRequest));
		}

		return req;
	}

	public void normalize(Page from, ClientOptions options) {
		String ct = getRequestHeaders().getContentType();
		String cs = HttpUtil.parseCharsetFromContentType(ct);
		if (cs == null && from != null)
			cs = from.getCharset();
		if (cs == null)
			cs = options.getDefaultCharset();

		setRequestCharset(cs);
	}

	@Override
	public OutputStream createResponseStream() {
		return new ByteArrayOutputStream();
	}

	@Override
	public void handleResponseContent(OutputStream outputStream,
			ClientOptions options) throws IOException {
		if (!(outputStream instanceof ByteArrayOutputStream))
			return;

		rawResponse = ((ByteArrayOutputStream) outputStream).toByteArray();
		outputStream.close();
	}

}
